package com.usbserial.transaction;

import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Emits frames whenever the configured terminator sequence is encountered.
 */
public class TerminatedFrameDecoder implements FrameDecoder<byte[]> {

    private final byte[] terminator;
    private final boolean stripTerminator;
    private final int maxBufferLength;
    private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();

    public TerminatedFrameDecoder(byte[] terminator, int maxBufferLength, boolean stripTerminator) {
        if (terminator == null || terminator.length == 0) {
            throw new IllegalArgumentException("Terminator must not be empty");
        }
        this.terminator = terminator.clone();
        this.maxBufferLength = Math.max(maxBufferLength, terminator.length * 2);
        this.stripTerminator = stripTerminator;
    }

    @Override
    public synchronized List<byte[]> decode(byte[] chunk) {
        if (chunk == null || chunk.length == 0) {
            return Collections.emptyList();
        }
        buffer.write(chunk, 0, chunk.length);
        truncateIfNeeded();
        List<byte[]> frames = new ArrayList<>();
        int index;
        while ((index = indexOfTerminator(buffer.toByteArray())) >= 0) {
            byte[] raw = buffer.toByteArray();
            int end = index + terminator.length;
            byte[] frame;
            if (stripTerminator) {
                frame = new byte[index];
                System.arraycopy(raw, 0, frame, 0, index);
            } else {
                frame = new byte[end];
                System.arraycopy(raw, 0, frame, 0, end);
            }
            frames.add(frame);
            ByteArrayOutputStream remaining = new ByteArrayOutputStream();
            remaining.write(raw, end, raw.length - end);
            buffer.reset();
            buffer.write(remaining.toByteArray(), 0, remaining.size());
        }
        return frames;
    }

    private int indexOfTerminator(byte[] haystack) {
        if (haystack.length < terminator.length) {
            return -1;
        }
        outer:
        for (int i = 0; i <= haystack.length - terminator.length; i++) {
            for (int j = 0; j < terminator.length; j++) {
                if (haystack[i + j] != terminator[j]) {
                    continue outer;
                }
            }
            return i;
        }
        return -1;
    }

    private void truncateIfNeeded() {
        byte[] raw = buffer.toByteArray();
        if (raw.length <= maxBufferLength) {
            return;
        }
        int keep = maxBufferLength;
        byte[] trimmed = new byte[keep];
        System.arraycopy(raw, raw.length - keep, trimmed, 0, keep);
        buffer.reset();
        buffer.write(trimmed, 0, trimmed.length);
    }

    @Override
    public synchronized void reset() {
        buffer.reset();
    }
}
